1   /*
2    * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.security.sasl.digest;
27  
28  import java.util.Map;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Set;
32  import java.util.logging.Logger;
33  import java.util.logging.Level;
34  import java.math.BigInteger;
35  import java.util.Random;
36  import java.security.Provider;
37  
38  import java.io.ByteArrayInputStream;
39  import java.io.ByteArrayOutputStream;
40  import java.io.UnsupportedEncodingException;
41  import java.io.IOException;
42  
43  import java.security.MessageDigest;
44  import java.security.AccessController;
45  import java.security.PrivilegedAction;
46  import java.security.NoSuchAlgorithmException;
47  import java.security.InvalidKeyException;
48  import java.security.spec.KeySpec;
49  import java.security.spec.InvalidKeySpecException;
50  import java.security.InvalidAlgorithmParameterException;
51  
52  import javax.crypto.Cipher;
53  import javax.crypto.SecretKey;
54  import javax.crypto.Mac;
55  import javax.crypto.SecretKeyFactory;
56  import javax.crypto.BadPaddingException;
57  import javax.crypto.NoSuchPaddingException;
58  import javax.crypto.IllegalBlockSizeException;
59  import javax.crypto.spec.IvParameterSpec;
60  import javax.crypto.spec.SecretKeySpec;
61  import javax.crypto.spec.DESKeySpec;
62  import javax.crypto.spec.DESedeKeySpec;
63  
64  import javax.security.sasl.*;
65  import com.sun.security.sasl.util.AbstractSaslImpl;
66  
67  import javax.security.auth.callback.CallbackHandler;
68  
69  /**
70   * Utility class for DIGEST-MD5 mechanism. Provides utility methods
71   * and contains two inner classes which implement the SecurityCtx
72   * interface. The inner classes provide the funtionality to allow
73   * for quality-of-protection (QOP) with integrity checking and
74   * privacy.
75   *
76   * @author Jonathan Bruce
77   * @author Rosanna Lee
78   */
79  abstract class DigestMD5Base extends AbstractSaslImpl {
80      /* ------------------------- Constants ------------------------ */
81  
82      // Used for logging
83      private static final String DI_CLASS_NAME = DigestIntegrity.class.getName();
84      private static final String DP_CLASS_NAME = DigestPrivacy.class.getName();
85  
86      /* Constants - defined in RFC2831 */
87      protected static final int MAX_CHALLENGE_LENGTH = 2048;
88      protected static final int MAX_RESPONSE_LENGTH = 4096;
89      protected static final int DEFAULT_MAXBUF = 65536;
90  
91      /* Supported ciphers for 'auth-conf' */
92      protected static final int DES3 = 0;
93      protected static final int RC4 = 1;
94      protected static final int DES = 2;
95      protected static final int RC4_56 = 3;
96      protected static final int RC4_40 = 4;
97      protected static final String[] CIPHER_TOKENS = { "3des",
98                                                        "rc4",
99                                                        "des",
100                                                       "rc4-56",
101                                                       "rc4-40" };
102     private static final String[] JCE_CIPHER_NAME = {
103         "DESede/CBC/NoPadding",
104         "RC4",
105         "DES/CBC/NoPadding",
106     };
107 
108     /*
109      * If QOP is set to 'auth-conf', a DIGEST-MD5 mechanism must have
110      * support for the DES and Triple DES cipher algorithms (optionally,
111      * support for RC4 [128/56/40 bit keys] ciphers) to provide for
112      * confidentiality. See RFC 2831 for details. This implementation
113      * provides support for DES, Triple DES and RC4 ciphers.
114      *
115      * The value of strength effects the strength of cipher used. The mappings
116      * of 'high', 'medium', and 'low' give the following behaviour.
117      *
118      *  HIGH_STRENGTH   - Triple DES
119      *                  - RC4 (128bit)
120      *  MEDIUM_STRENGTH - DES
121      *                  - RC4 (56bit)
122      *  LOW_SRENGTH     - RC4 (40bit)
123      */
124     protected static final byte DES_3_STRENGTH = HIGH_STRENGTH;
125     protected static final byte RC4_STRENGTH = HIGH_STRENGTH;
126     protected static final byte DES_STRENGTH = MEDIUM_STRENGTH;
127     protected static final byte RC4_56_STRENGTH = MEDIUM_STRENGTH;
128     protected static final byte RC4_40_STRENGTH = LOW_STRENGTH;
129     protected static final byte UNSET = (byte)0;
130     protected static final byte[] CIPHER_MASKS = { DES_3_STRENGTH,
131                                                    RC4_STRENGTH,
132                                                    DES_STRENGTH,
133                                                    RC4_56_STRENGTH,
134                                                    RC4_40_STRENGTH };
135 
136     private static final String SECURITY_LAYER_MARKER =
137         ":00000000000000000000000000000000";
138 
139     protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
140 
141     /* ------------------- Variable Fields ----------------------- */
142 
143     /* Used to track progress of authentication; step numbers from RFC 2831 */
144     protected int step;
145 
146     /* Used to get username/password, choose realm for client */
147     /* Used to obtain authorization, pw info, canonicalized authzid for server */
148     protected CallbackHandler cbh;
149 
150     protected SecurityCtx secCtx;
151     protected byte[] H_A1; // component of response-value
152 
153     protected byte[] nonce;         // server generated nonce
154 
155     /* Variables set when parsing directives in digest challenge/response. */
156     protected String negotiatedStrength;
157     protected String negotiatedCipher;
158     protected String negotiatedQop;
159     protected String negotiatedRealm;
160     protected boolean useUTF8 = false;
161     protected String encoding = "8859_1";  // default unless server specifies utf-8
162 
163     protected String digestUri;
164     protected String authzid;       // authzid or canonicalized authzid
165 
166     /**
167      * Constucts an instance of DigestMD5Base. Calls super constructor
168      * to parse properties for mechanism.
169      *
170      * @param props A map of property/value pairs
171      * @param className name of class to use for logging
172      * @param firstStep number of first step in authentication state machine
173      * @param digestUri digestUri used in authentication
174      * @param cbh callback handler used to get info required for auth
175      *
176      * @throws SaslException If invalid value found in props.
177      */
178     protected DigestMD5Base(Map props, String className, int firstStep,
179         String digestUri, CallbackHandler cbh) throws SaslException {
180         super(props, className); // sets QOP, STENGTH and BUFFER_SIZE
181 
182         step = firstStep;
183         this.digestUri = digestUri;
184         this.cbh = cbh;
185     }
186 
187     /**
188      * Retrieves the SASL mechanism IANA name.
189      *
190      * @return The String "DIGEST-MD5"
191      */
192     public String getMechanismName() {
193         return "DIGEST-MD5";
194     }
195 
196     /**
197      * Unwrap the incoming message using the wrap method of the secCtx object
198      * instance.
199      *
200      * @param incoming The byte array containing the incoming bytes.
201      * @param start The offset from which to read the byte array.
202      * @param len The number of bytes to read from the offset.
203      * @return The unwrapped message according to either the integrity or
204      * privacy quality-of-protection specifications.
205      * @throws SaslException if an error occurs when unwrapping the incoming
206      * message
207      */
208     public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException {
209         if (!completed) {
210             throw new IllegalStateException(
211                 "DIGEST-MD5 authentication not completed");
212         }
213 
214         if (secCtx == null) {
215             throw new IllegalStateException(
216                 "Neither integrity nor privacy was negotiated");
217         }
218 
219         return (secCtx.unwrap(incoming, start, len));
220     }
221 
222     /**
223      * Wrap outgoing bytes using the wrap method of the secCtx object
224      * instance.
225      *
226      * @param outgoing The byte array containing the outgoing bytes.
227      * @param start The offset from which to read the byte array.
228      * @param len The number of bytes to read from the offset.
229      * @return The wrapped message according to either the integrity or
230      * privacy quality-of-protection specifications.
231      * @throws SaslException if an error occurs when wrapping the outgoing
232      * message
233      */
234     public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException {
235         if (!completed) {
236             throw new IllegalStateException(
237                 "DIGEST-MD5 authentication not completed");
238         }
239 
240         if (secCtx == null) {
241             throw new IllegalStateException(
242                 "Neither integrity nor privacy was negotiated");
243         }
244 
245         return (secCtx.wrap(outgoing, start, len));
246     }
247 
248     public void dispose() throws SaslException {
249         if (secCtx != null) {
250             secCtx = null;
251         }
252     }
253 
254     public Object getNegotiatedProperty(String propName) {
255         if (completed) {
256             if (propName.equals(Sasl.STRENGTH)) {
257                 return negotiatedStrength;
258             } else {
259                 return super.getNegotiatedProperty(propName);
260             }
261         } else {
262             throw new IllegalStateException(
263                 "DIGEST-MD5 authentication not completed");
264         }
265     }
266 
267     /* ----------------- Digest-MD5 utilities ---------------- */
268     /**
269      * Generate random-string used for digest-response.
270      * This method uses Random to get random bytes and then
271      * base64 encodes the bytes. Could also use binaryToHex() but this
272      * is slightly faster and a more compact representation of the same info.
273      * @return A non-null byte array containing the nonce value for the
274      * digest challenge or response.
275      * Could use SecureRandom to be more secure but it is very slow.
276      */
277 
278     /** This array maps the characters to their 6 bit values */
279     private final static char pem_array[] = {
280         //       0   1   2   3   4   5   6   7
281                 'A','B','C','D','E','F','G','H', // 0
282                 'I','J','K','L','M','N','O','P', // 1
283                 'Q','R','S','T','U','V','W','X', // 2
284                 'Y','Z','a','b','c','d','e','f', // 3
285                 'g','h','i','j','k','l','m','n', // 4
286                 'o','p','q','r','s','t','u','v', // 5
287                 'w','x','y','z','0','1','2','3', // 6
288                 '4','5','6','7','8','9','+','/'  // 7
289     };
290 
291     // Make sure that this is a multiple of 3
292     private static final int RAW_NONCE_SIZE = 30;
293 
294     // Base 64 encoding turns each 3 bytes into 4
295     private static final int ENCODED_NONCE_SIZE = RAW_NONCE_SIZE*4/3;
296 
297     protected static final byte[] generateNonce() {
298 
299         // SecureRandom random = new SecureRandom();
300         Random random = new Random();
301         byte[] randomData = new byte[RAW_NONCE_SIZE];
302         random.nextBytes(randomData);
303 
304         byte[] nonce = new byte[ENCODED_NONCE_SIZE];
305 
306         // Base64-encode bytes
307         byte a, b, c;
308         int j = 0;
309         for (int i = 0; i < randomData.length; i += 3) {
310             a = randomData[i];
311             b = randomData[i+1];
312             c = randomData[i+2];
313             nonce[j++] = (byte)(pem_array[(a >>> 2) & 0x3F]);
314             nonce[j++] = (byte)(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
315             nonce[j++] = (byte)(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
316             nonce[j++] = (byte)(pem_array[c & 0x3F]);
317         }
318 
319         return nonce;
320 
321         // %%% For testing using RFC 2831 example, uncomment the following 2 lines
322         // System.out.println("!!!Using RFC 2831's cnonce for testing!!!");
323         // return "OA6MHXh6VqTrRk".getBytes();
324     }
325 
326     /**
327      * Checks if a byte[] contains characters that must be quoted
328      * and write the resulting, possibly escaped, characters to out.
329      */
330     protected static void writeQuotedStringValue(ByteArrayOutputStream out,
331         byte[] buf) {
332 
333         int len = buf.length;
334         byte ch;
335         for (int i = 0; i < len; i++) {
336             ch = buf[i];
337             if (needEscape((char)ch)) {
338                 out.write('\\');
339             }
340             out.write(ch);
341         }
342     }
343 
344     // See Section 7.2 of RFC 2831; double-quote character is not allowed
345     // unless escaped; also escape the escape character and CTL chars except LWS
346     private static boolean needEscape(String str) {
347         int len = str.length();
348         for (int i = 0; i < len; i++) {
349             if (needEscape(str.charAt(i))) {
350                 return true;
351             }
352         }
353         return false;
354     }
355 
356     // Determines whether a character needs to be escaped in a quoted string
357     private static boolean needEscape(char ch) {
358         return ch == '"' ||  // escape char
359             ch == '\\' ||    // quote
360             ch == 127 ||     // DEL
361 
362             // 0 <= ch <= 31 except CR, HT and LF
363             (ch >= 0 && ch <= 31 && ch != 13 && ch != 9 && ch != 10);
364     }
365 
366     protected static String quotedStringValue(String str) {
367         if (needEscape(str)) {
368             int len = str.length();
369             char[] buf = new char[len+len];
370             int j = 0;
371             char ch;
372             for (int i = 0; i < len; i++) {
373                 ch = str.charAt(i);
374                 if (needEscape(ch)) {
375                     buf[j++] =  '\\';
376                 }
377                 buf[j++] = ch;
378             }
379             return new String(buf, 0, j);
380         } else {
381             return str;
382         }
383     }
384 
385     /**
386      * Convert a byte array to hexadecimal string.
387      *
388      * @param a non-null byte array
389      * @return a non-null String contain the HEX value
390      */
391     protected byte[] binaryToHex(byte[] digest) throws
392     UnsupportedEncodingException {
393 
394         StringBuffer digestString = new StringBuffer();
395 
396         for (int i = 0; i < digest.length; i ++) {
397             if ((digest[i] & 0x000000ff) < 0x10) {
398                 digestString.append("0"+
399                     Integer.toHexString(digest[i] & 0x000000ff));
400             } else {
401                 digestString.append(
402                     Integer.toHexString(digest[i] & 0x000000ff));
403             }
404         }
405         return digestString.toString().getBytes(encoding);
406     }
407 
408     /**
409      * Used to convert username-value, passwd or realm to 8859_1 encoding
410      * if all chars in string are within the 8859_1 (Latin 1) encoding range.
411      *
412      * @param a non-null String
413      * @return a non-nuill byte array containing the correct character encoding
414      * for username, paswd or realm.
415      */
416     protected byte[] stringToByte_8859_1(String str) throws SaslException {
417 
418         char[] buffer = str.toCharArray();
419 
420         try {
421             if (useUTF8) {
422                 for( int i = 0; i< buffer.length; i++ ) {
423                     if( buffer[i] > '\u00FF' ) {
424                         return str.getBytes("UTF8");
425                     }
426                 }
427             }
428             return str.getBytes("8859_1");
429         } catch (UnsupportedEncodingException e) {
430             throw new SaslException(
431                 "cannot encode string in UTF8 or 8859-1 (Latin-1)", e);
432         }
433     }
434 
435     protected static byte[] getPlatformCiphers() {
436         byte[] ciphers = new byte[CIPHER_TOKENS.length];
437 
438         for (int i = 0; i < JCE_CIPHER_NAME.length; i++) {
439             try {
440                 // Checking whether the transformation is available from the
441                 // current installed providers.
442                 Cipher.getInstance(JCE_CIPHER_NAME[i]);
443 
444                 logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]);
445                 ciphers[i] |= CIPHER_MASKS[i];
446             } catch (NoSuchAlgorithmException e) {
447                 // no implementation found for requested algorithm.
448             } catch (NoSuchPaddingException e) {
449                 // no implementation found for requested algorithm.
450             }
451         }
452 
453         if (ciphers[RC4] != UNSET) {
454             ciphers[RC4_56] |= CIPHER_MASKS[RC4_56];
455             ciphers[RC4_40] |= CIPHER_MASKS[RC4_40];
456         }
457 
458         return ciphers;
459     }
460 
461     /**
462      * Assembles response-value for digest-response.
463      *
464      * @param authMethod "AUTHENTICATE" for client-generated response;
465      *        "" for server-generated response
466      * @return A non-null byte array containing the repsonse-value.
467      * @throws NoSuchAlgorithmException if the platform does not have MD5
468      * digest support.
469      * @throws UnsupportedEncodingException if a an error occurs
470      * encoding a string into either Latin-1 or UTF-8.
471      * @throws IOException if an error occurs writing to the output
472      * byte array buffer.
473      */
474     protected byte[] generateResponseValue(
475         String authMethod,
476         String digestUriValue,
477         String qopValue,
478         String usernameValue,
479         String realmValue,
480         char[] passwdValue,
481         byte[] nonceValue,
482         byte[] cNonceValue,
483         int nonceCount,
484         byte[] authzidValue
485         ) throws NoSuchAlgorithmException,
486             UnsupportedEncodingException,
487             IOException {
488 
489         MessageDigest md5 = MessageDigest.getInstance("MD5");
490         byte[] hexA1, hexA2;
491         ByteArrayOutputStream A2, beginA1, A1, KD;
492 
493         // A2
494         // --
495         // A2 = { "AUTHENTICATE:", digest-uri-value,
496         // [:00000000000000000000000000000000] }  // if auth-int or auth-conf
497         //
498         A2 = new ByteArrayOutputStream();
499         A2.write((authMethod + ":" + digestUriValue).getBytes(encoding));
500         if (qopValue.equals("auth-conf") ||
501             qopValue.equals("auth-int")) {
502 
503             logger.log(Level.FINE, "DIGEST04:QOP: {0}", qopValue);
504 
505             A2.write(SECURITY_LAYER_MARKER.getBytes(encoding));
506         }
507 
508         if (logger.isLoggable(Level.FINE)) {
509             logger.log(Level.FINE, "DIGEST05:A2: {0}", A2.toString());
510         }
511 
512         md5.update(A2.toByteArray());
513         byte[] digest = md5.digest();
514         hexA2 = binaryToHex(digest);
515 
516         if (logger.isLoggable(Level.FINE)) {
517             logger.log(Level.FINE, "DIGEST06:HEX(H(A2)): {0}", new String(hexA2));
518         }
519 
520         // A1
521         // --
522         // H(user-name : realm-value : passwd)
523         //
524         beginA1 = new ByteArrayOutputStream();
525         beginA1.write(stringToByte_8859_1(usernameValue));
526         beginA1.write(':');
527         // if no realm, realm will be an empty string
528         beginA1.write(stringToByte_8859_1(realmValue));
529         beginA1.write(':');
530         beginA1.write(stringToByte_8859_1(new String(passwdValue)));
531 
532         md5.update(beginA1.toByteArray());
533         digest = md5.digest();
534 
535         if (logger.isLoggable(Level.FINE)) {
536             logger.log(Level.FINE, "DIGEST07:H({0}) = {1}",
537                 new Object[]{beginA1.toString(), new String(binaryToHex(digest))});
538         }
539 
540         // A1
541         // --
542         // A1 = { H ( {user-name : realm-value : passwd } ),
543         // : nonce-value, : cnonce-value : authzid-value
544         //
545         A1 = new ByteArrayOutputStream();
546         A1.write(digest);
547         A1.write(':');
548         A1.write(nonceValue);
549         A1.write(':');
550         A1.write(cNonceValue);
551 
552         if (authzidValue != null) {
553             A1.write(':');
554             A1.write(authzidValue);
555         }
556         md5.update(A1.toByteArray());
557         digest = md5.digest();
558         H_A1 = digest; // Record H(A1). Use for integrity & privacy.
559         hexA1 = binaryToHex(digest);
560 
561         if (logger.isLoggable(Level.FINE)) {
562             logger.log(Level.FINE, "DIGEST08:H(A1) = {0}", new String(hexA1));
563         }
564 
565         //
566         // H(k, : , s);
567         //
568         KD = new ByteArrayOutputStream();
569         KD.write(hexA1);
570         KD.write(':');
571         KD.write(nonceValue);
572         KD.write(':');
573         KD.write(nonceCountToHex(nonceCount).getBytes(encoding));
574         KD.write(':');
575         KD.write(cNonceValue);
576         KD.write(':');
577         KD.write(qopValue.getBytes(encoding));
578         KD.write(':');
579         KD.write(hexA2);
580 
581         if (logger.isLoggable(Level.FINE)) {
582             logger.log(Level.FINE, "DIGEST09:KD: {0}", KD.toString());
583         }
584 
585         md5.update(KD.toByteArray());
586         digest = md5.digest();
587 
588         byte[] answer = binaryToHex(digest);
589 
590         if (logger.isLoggable(Level.FINE)) {
591             logger.log(Level.FINE, "DIGEST10:response-value: {0}",
592                 new String(answer));
593         }
594         return (answer);
595     }
596 
597     /**
598      * Takes 'nonceCount' value and returns HEX value of the value.
599      *
600      * @return A non-null String representing the current NONCE-COUNT
601      */
602     protected static String nonceCountToHex(int count) {
603 
604         String str = Integer.toHexString(count);
605         StringBuffer pad = new StringBuffer();
606 
607         if (str.length() < 8) {
608             for (int i = 0; i < 8-str.length(); i ++) {
609                 pad.append("0");
610             }
611         }
612 
613         return pad.toString() + str;
614     }
615 
616     /**
617      * Parses digest-challenge string, extracting each token
618      * and value(s)
619      *
620      * @param buf A non-null digest-challenge string.
621      * @param multipleAllowed true if multiple qop or realm or QOP directives
622      *  are allowed.
623      * @throws SaslException if the buf cannot be parsed according to RFC 2831
624      */
625     protected static byte[][] parseDirectives(byte[] buf,
626         String[]keyTable, List<byte[]> realmChoices, int realmIndex) throws SaslException {
627 
628         byte[][] valueTable = new byte[keyTable.length][];
629 
630         ByteArrayOutputStream key = new ByteArrayOutputStream(10);
631         ByteArrayOutputStream value = new ByteArrayOutputStream(10);
632         boolean gettingKey = true;
633         boolean gettingQuotedValue = false;
634         boolean expectSeparator = false;
635         byte bch;
636 
637         int i = skipLws(buf, 0);
638         while (i < buf.length) {
639             bch = buf[i];
640 
641             if (gettingKey) {
642                 if (bch == ',') {
643                     if (key.size() != 0) {
644                         throw new SaslException("Directive key contains a ',':" +
645                             key);
646                     }
647                     // Empty element, skip separator and lws
648                     i = skipLws(buf, i+1);
649 
650                 } else if (bch == '=') {
651                     if (key.size() == 0) {
652                         throw new SaslException("Empty directive key");
653                     }
654                     gettingKey = false;      // Termination of key
655                     i = skipLws(buf, i+1);   // Skip to next nonwhitespace
656 
657                     // Check whether value is quoted
658                     if (i < buf.length) {
659                         if (buf[i] == '"') {
660                             gettingQuotedValue = true;
661                             ++i; // Skip quote
662                         }
663                     } else {
664                         throw new SaslException(
665                             "Valueless directive found: " + key.toString());
666                     }
667                 } else if (isLws(bch)) {
668                     // LWS that occurs after key
669                     i = skipLws(buf, i+1);
670 
671                     // Expecting '='
672                     if (i < buf.length) {
673                         if (buf[i] != '=') {
674                             throw new SaslException("'=' expected after key: " +
675                                 key.toString());
676                         }
677                     } else {
678                         throw new SaslException(
679                             "'=' expected after key: " + key.toString());
680                     }
681                 } else {
682                     key.write(bch);    // Append to key
683                     ++i;               // Advance
684                 }
685             } else if (gettingQuotedValue) {
686                 // Getting a quoted value
687                 if (bch == '\\') {
688                     // quoted-pair = "\" CHAR  ==> CHAR
689                     ++i;       // Skip escape
690                     if (i < buf.length) {
691                         value.write(buf[i]);
692                         ++i;   // Advance
693                     } else {
694                         // Trailing escape in a quoted value
695                         throw new SaslException(
696                             "Unmatched quote found for directive: "
697                             + key.toString() + " with value: " + value.toString());
698                     }
699                 } else if (bch == '"') {
700                     // closing quote
701                     ++i;  // Skip closing quote
702                     gettingQuotedValue = false;
703                     expectSeparator = true;
704                 } else {
705                     value.write(bch);
706                     ++i;  // Advance
707                 }
708 
709             } else if (isLws(bch) || bch == ',') {
710                 //  Value terminated
711 
712                 extractDirective(key.toString(), value.toByteArray(),
713                     keyTable, valueTable, realmChoices, realmIndex);
714                 key.reset();
715                 value.reset();
716                 gettingKey = true;
717                 gettingQuotedValue = expectSeparator = false;
718                 i = skipLws(buf, i+1);   // Skip separator and LWS
719 
720             } else if (expectSeparator) {
721                 throw new SaslException(
722                     "Expecting comma or linear whitespace after quoted string: \""
723                         + value.toString() + "\"");
724             } else {
725                 value.write(bch);   // Unquoted value
726                 ++i;                // Advance
727             }
728         }
729 
730         if (gettingQuotedValue) {
731             throw new SaslException(
732                 "Unmatched quote found for directive: " + key.toString() +
733                 " with value: " + value.toString());
734         }
735 
736         // Get last pair
737         if (key.size() > 0) {
738             extractDirective(key.toString(), value.toByteArray(),
739                 keyTable, valueTable, realmChoices, realmIndex);
740         }
741 
742         return valueTable;
743     }
744 
745     // Is character a linear white space?
746     // LWS            = [CRLF] 1*( SP | HT )
747     // %%% Note that we're checking individual bytes instead of CRLF
748     private static boolean isLws(byte b) {
749         switch (b) {
750         case 13:   // US-ASCII CR, carriage return
751         case 10:   // US-ASCII LF, linefeed
752         case 32:   // US-ASCII SP, space
753         case 9:    // US-ASCII HT, horizontal-tab
754             return true;
755         }
756         return false;
757     }
758 
759     // Skip all linear white spaces
760     private static int skipLws(byte[] buf, int start) {
761         int i;
762         for (i = start; i < buf.length; i++) {
763             if (!isLws(buf[i])) {
764                 return i;
765             }
766         }
767         return i;
768     }
769 
770     /**
771      * Processes directive/value pairs from the digest-challenge and
772      * fill out the challengeVal array.
773      *
774      * @param key A non-null String challenge token name.
775      * @param value A non-null String token value.
776      * @throws SaslException if a either the key or the value is null
777      */
778     private static void  extractDirective(String key, byte[] value,
779         String[] keyTable, byte[][] valueTable,
780         List<byte[]> realmChoices, int realmIndex) throws SaslException {
781 
782         for (int i = 0; i < keyTable.length; i++) {
783             if (key.equalsIgnoreCase(keyTable[i])) {
784                 if (valueTable[i] == null) {
785                     valueTable[i] = value;
786                     if (logger.isLoggable(Level.FINE)) {
787                         logger.log(Level.FINE, "DIGEST11:Directive {0} = {1}",
788                             new Object[]{
789                                 keyTable[i],
790                                 new String(valueTable[i])});
791                     }
792                 } else if (realmChoices != null && i == realmIndex) {
793                     // > 1 realm specified
794                     if (realmChoices.size() == 0) {
795                         realmChoices.add(valueTable[i]); // add existing one
796                     }
797                     realmChoices.add(value);  // add new one
798                 } else {
799                     throw new SaslException(
800                         "DIGEST-MD5: peer sent more than one " +
801                         key + " directive: " + new String(value));
802                 }
803 
804                 break; // end search
805             }
806         }
807      }
808 
809 
810     /**
811      * Implementation of the SecurityCtx interface allowing for messages
812      * between the client and server to be integrity checked. After a
813      * successful DIGEST-MD5 authentication, integtrity checking is invoked
814      * if the SASL QOP (quality-of-protection) is set to 'auth-int'.
815      * <p>
816      * Further details on the integrity-protection mechanism can be found
817      * at section 2.3 - Integrity protection in the
818      * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition.
819      *
820      * @author Jonathan Bruce
821      */
822     class DigestIntegrity implements SecurityCtx {
823         /* Used for generating integrity keys - specified in RFC 2831*/
824         static final private String CLIENT_INT_MAGIC = "Digest session key to " +
825             "client-to-server signing key magic constant";
826         static final private String SVR_INT_MAGIC = "Digest session key to " +
827             "server-to-client signing key magic constant";
828 
829         /* Key pairs for integrity checking */
830         protected byte[] myKi;     // == Kic for client; == Kis for server
831         protected byte[] peerKi;   // == Kis for client; == Kic for server
832 
833         protected int mySeqNum = 0;
834         protected int peerSeqNum = 0;
835 
836         // outgoing messageType and sequenceNum
837         protected final byte[] messageType = new byte[2];
838         protected final byte[] sequenceNum = new byte[4];
839 
840         /**
841          * Initializes DigestIntegrity implementation of SecurityCtx to
842          * enable DIGEST-MD5 integrity checking.
843          *
844          * @throws SaslException if an error is encountered generating the
845          * key-pairs for integrity checking.
846          */
847         DigestIntegrity(boolean clientMode) throws SaslException {
848             /* Initialize magic strings */
849 
850             try {
851                 generateIntegrityKeyPair(clientMode);
852 
853             } catch (UnsupportedEncodingException e) {
854                 throw new SaslException(
855                     "DIGEST-MD5: Error encoding strings into UTF-8", e);
856 
857             } catch (IOException e) {
858                 throw new SaslException("DIGEST-MD5: Error accessing buffers " +
859                     "required to create integrity key pairs", e);
860 
861             } catch (NoSuchAlgorithmException e) {
862                 throw new SaslException("DIGEST-MD5: Unsupported digest " +
863                     "algorithm used to create integrity key pairs", e);
864             }
865 
866             /* Message type is a fixed value */
867             intToNetworkByteOrder(1, messageType, 0, 2);
868         }
869 
870         /**
871          * Generate client-server, server-client key pairs for DIGEST-MD5
872          * integrity checking.
873          *
874          * @throws UnsupportedEncodingException if the UTF-8 encoding is not
875          * supported on the platform.
876          * @throws IOException if an error occurs when writing to or from the
877          * byte array output buffers.
878          * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
879          * cannot loaded.
880          */
881         private void generateIntegrityKeyPair(boolean clientMode)
882             throws UnsupportedEncodingException, IOException,
883                 NoSuchAlgorithmException {
884 
885             byte[] cimagic = CLIENT_INT_MAGIC.getBytes(encoding);
886             byte[] simagic = SVR_INT_MAGIC.getBytes(encoding);
887 
888             MessageDigest md5 = MessageDigest.getInstance("MD5");
889 
890             // Both client-magic-keys and server-magic-keys are the same length
891             byte[] keyBuffer = new byte[H_A1.length + cimagic.length];
892 
893             // Kic: Key for protecting msgs from client to server.
894             System.arraycopy(H_A1, 0, keyBuffer, 0, H_A1.length);
895             System.arraycopy(cimagic, 0, keyBuffer, H_A1.length, cimagic.length);
896             md5.update(keyBuffer);
897             byte[] Kic = md5.digest();
898 
899             // Kis: Key for protecting msgs from server to client
900             // No need to recopy H_A1
901             System.arraycopy(simagic, 0, keyBuffer, H_A1.length, simagic.length);
902 
903             md5.update(keyBuffer);
904             byte[] Kis = md5.digest();
905 
906             if (logger.isLoggable(Level.FINER)) {
907                 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
908                     "DIGEST12:Kic: ", Kic);
909                 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
910                     "DIGEST13:Kis: ", Kis);
911             }
912 
913             if (clientMode) {
914                 myKi = Kic;
915                 peerKi = Kis;
916             } else {
917                 myKi = Kis;
918                 peerKi = Kic;
919             }
920         }
921 
922         /**
923          * Append MAC onto outgoing message.
924          *
925          * @param outgoing A non-null byte array containing the outgoing message.
926          * @param start The offset from which to read the byte array.
927          * @param len The non-zero number of bytes for be read from the offset.
928          * @return The message including the integrity MAC
929          * @throws SaslException if an error is encountered converting a string
930          * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
931          * cannot be found or if there is an error writing to the byte array
932          * output buffers.
933          */
934         public byte[] wrap(byte[] outgoing, int start, int len)
935             throws SaslException {
936 
937             if (len == 0) {
938                 return EMPTY_BYTE_ARRAY;
939             }
940 
941             /* wrapped = message, MAC, message type, sequence number */
942             byte[] wrapped = new byte[len+10+2+4];
943 
944             /* Start with message itself */
945             System.arraycopy(outgoing, start, wrapped, 0, len);
946 
947             incrementSeqNum();
948 
949             /* Calculate MAC */
950             byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
951 
952             if (logger.isLoggable(Level.FINEST)) {
953                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST14:outgoing: ",
954                     outgoing, start, len);
955                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST15:seqNum: ",
956                     sequenceNum);
957                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST16:MAC: ", mac);
958             }
959 
960             /* Add MAC[0..9] to message */
961             System.arraycopy(mac, 0, wrapped, len, 10);
962 
963             /* Add message type [0..1] */
964             System.arraycopy(messageType, 0, wrapped, len+10, 2);
965 
966             /* Add sequence number [0..3] */
967             System.arraycopy(sequenceNum, 0, wrapped, len+12, 4);
968             if (logger.isLoggable(Level.FINEST)) {
969                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST17:wrapped: ", wrapped);
970             }
971             return wrapped;
972         }
973 
974         /**
975          * Return verified message without MAC - only if the received MAC
976          * and re-generated MAC are the same.
977          *
978          * @param incoming A non-null byte array containing the incoming
979          * message.
980          * @param start The offset from which to read the byte array.
981          * @param len The non-zero number of bytes to read from the offset
982          * position.
983          * @return The verified message or null if integrity checking fails.
984          * @throws SaslException if an error is encountered converting a string
985          * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
986          * cannot be found or if there is an error writing to the byte array
987          * output buffers
988          */
989         public byte[] unwrap(byte[] incoming, int start, int len)
990             throws SaslException {
991 
992             if (len == 0) {
993                 return EMPTY_BYTE_ARRAY;
994             }
995 
996             // shave off last 16 bytes of message
997             byte[] mac = new byte[10];
998             byte[] msg = new byte[len - 16];
999             byte[] msgType = new byte[2];
1000             byte[] seqNum = new byte[4];
1001 
1002             /* Get Msg, MAC, msgType, sequenceNum */
1003             System.arraycopy(incoming, start, msg, 0, msg.length);
1004             System.arraycopy(incoming, start+msg.length, mac, 0, 10);
1005             System.arraycopy(incoming, start+msg.length+10, msgType, 0, 2);
1006             System.arraycopy(incoming, start+msg.length+12, seqNum, 0, 4);
1007 
1008             /* Calculate MAC to ensure integrity */
1009             byte[] expectedMac = getHMAC(peerKi, seqNum, msg, 0, msg.length);
1010 
1011             if (logger.isLoggable(Level.FINEST)) {
1012                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST18:incoming: ",
1013                     msg);
1014                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST19:MAC: ",
1015                     mac);
1016                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST20:messageType: ",
1017                     msgType);
1018                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST21:sequenceNum: ",
1019                     seqNum);
1020                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST22:expectedMAC: ",
1021                     expectedMac);
1022             }
1023 
1024             /* First, compare MAC's before updating any of our state */
1025             if (!Arrays.equals(mac, expectedMac)) {
1026                 //  Discard message and do not increment sequence number
1027                 logger.log(Level.INFO, "DIGEST23:Unmatched MACs");
1028                 return EMPTY_BYTE_ARRAY;
1029             }
1030 
1031             /* Ensure server-sequence numbers are correct */
1032             if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
1033                 throw new SaslException("DIGEST-MD5: Out of order " +
1034                     "sequencing of messages from server. Got: " +
1035                     networkByteOrderToInt(seqNum, 0, 4) +
1036                     " Expected: " +     peerSeqNum);
1037             }
1038 
1039             if (!Arrays.equals(messageType, msgType)) {
1040                 throw new SaslException("DIGEST-MD5: invalid message type: " +
1041                     networkByteOrderToInt(msgType, 0, 2));
1042             }
1043 
1044             // Increment sequence number and return message
1045             peerSeqNum++;
1046             return msg;
1047         }
1048 
1049         /**
1050          * Generates MAC to be appended onto out-going messages.
1051          *
1052          * @param Ki A non-null byte array containing the key for the digest
1053          * @param SeqNum A non-null byte array contain the sequence number
1054          * @param msg  The message to be digested
1055          * @param start The offset from which to read the msg byte array
1056          * @param len The non-zero number of bytes to be read from the offset
1057          * @return The MAC of a message.
1058          *
1059          * @throws SaslException if an error occurs when generating MAC.
1060          */
1061         protected byte[] getHMAC(byte[] Ki, byte[] seqnum, byte[] msg,
1062             int start, int len) throws SaslException {
1063 
1064             byte[] seqAndMsg = new byte[4+len];
1065             System.arraycopy(seqnum, 0, seqAndMsg, 0, 4);
1066             System.arraycopy(msg, start, seqAndMsg, 4, len);
1067 
1068             try {
1069                 SecretKey keyKi = new SecretKeySpec(Ki, "HmacMD5");
1070                 Mac m = Mac.getInstance("HmacMD5");
1071                 m.init(keyKi);
1072                 m.update(seqAndMsg);
1073                 byte[] hMAC_MD5 = m.doFinal();
1074 
1075                 /* First 10 bytes of HMAC_MD5 digest */
1076                 byte macBuffer[] = new byte[10];
1077                 System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10);
1078 
1079                 return macBuffer;
1080             } catch (InvalidKeyException e) {
1081                 throw new SaslException("DIGEST-MD5: Invalid bytes used for " +
1082                     "key of HMAC-MD5 hash.", e);
1083             } catch (NoSuchAlgorithmException e) {
1084                 throw new SaslException("DIGEST-MD5: Error creating " +
1085                     "instance of MD5 digest algorithm", e);
1086             }
1087         }
1088 
1089         /**
1090          * Increment own sequence number and set answer in NBO sequenceNum field.
1091          */
1092         protected void incrementSeqNum() {
1093             intToNetworkByteOrder(mySeqNum++, sequenceNum, 0, 4);
1094         }
1095     }
1096 
1097     /**
1098      * Implementation of the SecurityCtx interface allowing for messages
1099      * between the client and server to be integrity checked and encrypted.
1100      * After a successful DIGEST-MD5 authentication, privacy is invoked if the
1101      * SASL QOP (quality-of-protection) is set to 'auth-conf'.
1102      * <p>
1103      * Further details on the integrity-protection mechanism can be found
1104      * at section 2.4 - Confidentiality protection in
1105      * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition.
1106      *
1107      * @author Jonathan Bruce
1108      */
1109     final class DigestPrivacy extends DigestIntegrity implements SecurityCtx {
1110         /* Used for generating privacy keys - specified in RFC 2831 */
1111         static final private String CLIENT_CONF_MAGIC =
1112             "Digest H(A1) to client-to-server sealing key magic constant";
1113         static final private String SVR_CONF_MAGIC =
1114             "Digest H(A1) to server-to-client sealing key magic constant";
1115 
1116         private Cipher encCipher;
1117         private Cipher decCipher;
1118 
1119         /**
1120          * Initializes the cipher object instances for encryption and decryption.
1121          *
1122          * @throws SaslException if an error occurs with the Key
1123          * initialization, or a string cannot be encoded into a byte array
1124          * using the UTF-8 encoding, or an error occurs when writing to a
1125          * byte array output buffers or the mechanism cannot load the MD5
1126          * message digest algorithm or invalid initialization parameters are
1127          * passed to the cipher object instances.
1128          */
1129         DigestPrivacy(boolean clientMode) throws SaslException {
1130 
1131             super(clientMode); // generate Kic, Kis keys for integrity-checking.
1132 
1133             try {
1134                 generatePrivacyKeyPair(clientMode);
1135 
1136             } catch (SaslException e) {
1137                 throw e;
1138 
1139             } catch (UnsupportedEncodingException e) {
1140                 throw new SaslException(
1141                     "DIGEST-MD5: Error encoding string value into UTF-8", e);
1142 
1143             } catch (IOException e) {
1144                 throw new SaslException("DIGEST-MD5: Error accessing " +
1145                     "buffers required to generate cipher keys", e);
1146             } catch (NoSuchAlgorithmException e) {
1147                 throw new SaslException("DIGEST-MD5: Error creating " +
1148                     "instance of required cipher or digest", e);
1149             }
1150         }
1151 
1152         /**
1153          * Generates client-server and server-client keys to encrypt and
1154          * decrypt messages. Also generates IVs for DES ciphers.
1155          *
1156          * @throws IOException if an error occurs when writing to or from the
1157          * byte array output buffers.
1158          * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
1159          * cannot loaded.
1160          * @throws UnsupportedEncodingException if an UTF-8 encoding is not
1161          * supported on the platform.
1162          * @throw SaslException if an error occurs initializing the keys and
1163          * IVs for the chosen cipher.
1164          */
1165         private void generatePrivacyKeyPair(boolean clientMode)
1166             throws IOException, UnsupportedEncodingException,
1167             NoSuchAlgorithmException, SaslException {
1168 
1169             byte[] ccmagic = CLIENT_CONF_MAGIC.getBytes(encoding);
1170             byte[] scmagic = SVR_CONF_MAGIC.getBytes(encoding);
1171 
1172             /* Kcc = MD5{H(A1)[0..n], "Digest ... client-to-server"} */
1173             MessageDigest md5 = MessageDigest.getInstance("MD5");
1174 
1175             int n;
1176             if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_40])) {
1177                 n = 5;          /* H(A1)[0..5] */
1178             } else if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_56])) {
1179                 n = 7;          /* H(A1)[0..7] */
1180             } else { // des and 3des and rc4
1181                 n = 16;         /* H(A1)[0..16] */
1182             }
1183 
1184             /* {H(A1)[0..n], "Digest ... client-to-server..."} */
1185             // Both client-magic-keys and server-magic-keys are the same length
1186             byte[] keyBuffer =  new byte[n + ccmagic.length];
1187             System.arraycopy(H_A1, 0, keyBuffer, 0, n);   // H(A1)[0..n]
1188 
1189             /* Kcc: Key for encrypting messages from client->server */
1190             System.arraycopy(ccmagic, 0, keyBuffer, n, ccmagic.length);
1191             md5.update(keyBuffer);
1192             byte[] Kcc = md5.digest();
1193 
1194             /* Kcs: Key for decrypting messages from server->client */
1195             // No need to copy H_A1 again since it hasn't changed
1196             System.arraycopy(scmagic, 0, keyBuffer, n, scmagic.length);
1197             md5.update(keyBuffer);
1198             byte[] Kcs = md5.digest();
1199 
1200             if (logger.isLoggable(Level.FINER)) {
1201                 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1202                     "DIGEST24:Kcc: ", Kcc);
1203                 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1204                     "DIGEST25:Kcs: ", Kcs);
1205             }
1206 
1207             byte[] myKc;
1208             byte[] peerKc;
1209 
1210             if (clientMode) {
1211                 myKc = Kcc;
1212                 peerKc = Kcs;
1213             } else {
1214                 myKc = Kcs;
1215                 peerKc = Kcc;
1216             }
1217 
1218             try {
1219                 SecretKey encKey;
1220                 SecretKey decKey;
1221 
1222                 /* Initialize cipher objects */
1223                 if (negotiatedCipher.indexOf(CIPHER_TOKENS[RC4]) > -1) {
1224                     encCipher = Cipher.getInstance("RC4");
1225                     decCipher = Cipher.getInstance("RC4");
1226 
1227                     encKey = new SecretKeySpec(myKc, "RC4");
1228                     decKey = new SecretKeySpec(peerKc, "RC4");
1229 
1230                     encCipher.init(Cipher.ENCRYPT_MODE, encKey);
1231                     decCipher.init(Cipher.DECRYPT_MODE, decKey);
1232 
1233                 } else if ((negotiatedCipher.equals(CIPHER_TOKENS[DES])) ||
1234                     (negotiatedCipher.equals(CIPHER_TOKENS[DES3]))) {
1235 
1236                     // DES or 3DES
1237                     String cipherFullname, cipherShortname;
1238 
1239                         // Use "NoPadding" when specifying cipher names
1240                         // RFC 2831 already defines padding rules for producing
1241                         // 8-byte aligned blocks
1242                     if (negotiatedCipher.equals(CIPHER_TOKENS[DES])) {
1243                         cipherFullname = "DES/CBC/NoPadding";
1244                         cipherShortname = "des";
1245                     } else {
1246                         /* 3DES */
1247                         cipherFullname = "DESede/CBC/NoPadding";
1248                         cipherShortname = "desede";
1249                     }
1250 
1251                     encCipher = Cipher.getInstance(cipherFullname);
1252                     decCipher = Cipher.getInstance(cipherFullname);
1253 
1254                     encKey = makeDesKeys(myKc, cipherShortname);
1255                     decKey = makeDesKeys(peerKc, cipherShortname);
1256 
1257                     // Set up the DES IV, which is the last 8 bytes of Kcc/Kcs
1258                     IvParameterSpec encIv = new IvParameterSpec(myKc, 8, 8);
1259                     IvParameterSpec decIv = new IvParameterSpec(peerKc, 8, 8);
1260 
1261                     // Initialize cipher objects
1262                     encCipher.init(Cipher.ENCRYPT_MODE, encKey, encIv);
1263                     decCipher.init(Cipher.DECRYPT_MODE, decKey, decIv);
1264 
1265                     if (logger.isLoggable(Level.FINER)) {
1266                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1267                             "DIGEST26:" + negotiatedCipher + " IVcc: ",
1268                             encIv.getIV());
1269                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1270                             "DIGEST27:" + negotiatedCipher + " IVcs: ",
1271                             decIv.getIV());
1272                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1273                             "DIGEST28:" + negotiatedCipher + " encryption key: ",
1274                             encKey.getEncoded());
1275                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1276                             "DIGEST29:" + negotiatedCipher + " decryption key: ",
1277                             decKey.getEncoded());
1278                     }
1279                 }
1280             } catch (InvalidKeySpecException e) {
1281                 throw new SaslException("DIGEST-MD5: Unsupported key " +
1282                     "specification used.", e);
1283             } catch (InvalidAlgorithmParameterException e) {
1284                 throw new SaslException("DIGEST-MD5: Invalid cipher " +
1285                     "algorithem parameter used to create cipher instance", e);
1286             } catch (NoSuchPaddingException e) {
1287                 throw new SaslException("DIGEST-MD5: Unsupported " +
1288                     "padding used for chosen cipher", e);
1289             } catch (InvalidKeyException e) {
1290                 throw new SaslException("DIGEST-MD5: Invalid data " +
1291                     "used to initialize keys", e);
1292             }
1293         }
1294 
1295         // -------------------------------------------------------------------
1296 
1297         /**
1298          * Encrypt out-going message.
1299          *
1300          * @param outgoing A non-null byte array containing the outgoing message.
1301          * @param start The offset from which to read the byte array.
1302          * @param len The non-zero number of bytes to be read from the offset.
1303          * @return The encrypted message.
1304          *
1305          * @throws SaslException if an error occurs when writing to or from the
1306          * byte array output buffers or if the MD5 message digest algorithm
1307          * cannot loaded or if an UTF-8 encoding is not supported on the
1308          * platform.
1309          */
1310         public byte[] wrap(byte[] outgoing, int start, int len)
1311             throws SaslException {
1312 
1313             if (len == 0) {
1314                 return EMPTY_BYTE_ARRAY;
1315             }
1316 
1317             /* HMAC(Ki, {SeqNum, msg})[0..9] */
1318             incrementSeqNum();
1319             byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
1320 
1321             if (logger.isLoggable(Level.FINEST)) {
1322                 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST30:Outgoing: ",
1323                     outgoing, start, len);
1324                 traceOutput(DP_CLASS_NAME, "wrap", "seqNum: ",
1325                     sequenceNum);
1326                 traceOutput(DP_CLASS_NAME, "wrap", "MAC: ", mac);
1327             }
1328 
1329             // Calculate padding
1330             int bs = encCipher.getBlockSize();
1331             byte[] padding;
1332             if (bs > 1 ) {
1333                 int pad = bs - ((len + 10) % bs); // add 10 for HMAC[0..9]
1334                 padding = new byte[pad];
1335                 for (int i=0; i < pad; i++) {
1336                     padding[i] = (byte)pad;
1337                 }
1338             } else {
1339                 padding = EMPTY_BYTE_ARRAY;
1340             }
1341 
1342             byte[] toBeEncrypted = new byte[len+padding.length+10];
1343 
1344             /* {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])} */
1345             System.arraycopy(outgoing, start, toBeEncrypted, 0, len);
1346             System.arraycopy(padding, 0, toBeEncrypted, len, padding.length);
1347             System.arraycopy(mac, 0, toBeEncrypted, len+padding.length, 10);
1348 
1349             if (logger.isLoggable(Level.FINEST)) {
1350                 traceOutput(DP_CLASS_NAME, "wrap",
1351                     "DIGEST31:{msg, pad, KicMAC}: ", toBeEncrypted);
1352             }
1353 
1354             /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
1355             byte[] cipherBlock;
1356             try {
1357                 // Do CBC (chaining) across packets
1358                 cipherBlock = encCipher.update(toBeEncrypted);
1359 
1360                 if (cipherBlock == null) {
1361                     // update() can return null
1362                     throw new IllegalBlockSizeException(""+toBeEncrypted.length);
1363                 }
1364             } catch (IllegalBlockSizeException e) {
1365                 throw new SaslException(
1366                     "DIGEST-MD5: Invalid block size for cipher", e);
1367             }
1368 
1369             byte[] wrapped = new byte[cipherBlock.length+2+4];
1370             System.arraycopy(cipherBlock, 0, wrapped, 0, cipherBlock.length);
1371             System.arraycopy(messageType, 0, wrapped, cipherBlock.length, 2);
1372             System.arraycopy(sequenceNum, 0, wrapped, cipherBlock.length+2, 4);
1373 
1374             if (logger.isLoggable(Level.FINEST)) {
1375                 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST32:Wrapped: ", wrapped);
1376             }
1377 
1378             return wrapped;
1379         }
1380 
1381         /*
1382          * Decrypt incoming messages and verify their integrity.
1383          *
1384          * @param incoming A non-null byte array containing the incoming
1385          * encrypted message.
1386          * @param start The offset from which to read the byte array.
1387          * @param len The non-zero number of bytes to read from the offset
1388          * position.
1389          * @return The decrypted, verified message or null if integrity
1390          * checking
1391          * fails.
1392          * @throws SaslException if there are the SASL buffer is empty or if
1393          * if an error occurs reading the SASL buffer.
1394          */
1395         public byte[] unwrap(byte[] incoming, int start, int len)
1396             throws SaslException {
1397 
1398             if (len == 0) {
1399                 return EMPTY_BYTE_ARRAY;
1400             }
1401 
1402             byte[] encryptedMsg = new byte[len - 6];
1403             byte[] msgType = new byte[2];
1404             byte[] seqNum = new byte[4];
1405 
1406             /* Get cipherMsg; msgType; sequenceNum */
1407             System.arraycopy(incoming, start,
1408                 encryptedMsg, 0, encryptedMsg.length);
1409             System.arraycopy(incoming, start+encryptedMsg.length,
1410                 msgType, 0, 2);
1411             System.arraycopy(incoming, start+encryptedMsg.length+2,
1412                 seqNum, 0, 4);
1413 
1414             if (logger.isLoggable(Level.FINEST)) {
1415                 logger.log(Level.FINEST,
1416                     "DIGEST33:Expecting sequence num: {0}",
1417                     new Integer(peerSeqNum));
1418                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST34:incoming: ",
1419                     encryptedMsg);
1420             }
1421 
1422             // Decrypt message
1423             /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
1424             byte[] decryptedMsg;
1425 
1426             try {
1427                 // Do CBC (chaining) across packets
1428                 decryptedMsg = decCipher.update(encryptedMsg);
1429 
1430                 if (decryptedMsg == null) {
1431                     // update() can return null
1432                     throw new IllegalBlockSizeException(""+encryptedMsg.length);
1433                 }
1434             } catch (IllegalBlockSizeException e) {
1435                 throw new SaslException("DIGEST-MD5: Illegal block " +
1436                     "sizes used with chosen cipher", e);
1437             }
1438 
1439             byte[] msgWithPadding = new byte[decryptedMsg.length - 10];
1440             byte[] mac = new byte[10];
1441 
1442             System.arraycopy(decryptedMsg, 0,
1443                 msgWithPadding, 0, msgWithPadding.length);
1444             System.arraycopy(decryptedMsg, msgWithPadding.length,
1445                 mac, 0, 10);
1446 
1447             if (logger.isLoggable(Level.FINEST)) {
1448                 traceOutput(DP_CLASS_NAME, "unwrap",
1449                     "DIGEST35:Unwrapped (w/padding): ", msgWithPadding);
1450                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST36:MAC: ", mac);
1451                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST37:messageType: ",
1452                     msgType);
1453                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST38:sequenceNum: ",
1454                     seqNum);
1455             }
1456 
1457             int msgLength = msgWithPadding.length;
1458             int blockSize = decCipher.getBlockSize();
1459             if (blockSize > 1) {
1460                 // get value of last octet of the byte array
1461                 msgLength -= (int)msgWithPadding[msgWithPadding.length - 1];
1462                 if (msgLength < 0) {
1463                     //  Discard message and do not increment sequence number
1464                     if (logger.isLoggable(Level.INFO)) {
1465                         logger.log(Level.INFO,
1466                             "DIGEST39:Incorrect padding: {0}",
1467                             new Byte(msgWithPadding[msgWithPadding.length - 1]));
1468                     }
1469                     return EMPTY_BYTE_ARRAY;
1470                 }
1471             }
1472 
1473             /* Re-calculate MAC to ensure integrity */
1474             byte[] expectedMac = getHMAC(peerKi, seqNum, msgWithPadding,
1475                 0, msgLength);
1476 
1477             if (logger.isLoggable(Level.FINEST)) {
1478                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST40:KisMAC: ",
1479                     expectedMac);
1480             }
1481 
1482             // First, compare MACs before updating state
1483             if (!Arrays.equals(mac, expectedMac)) {
1484                 //  Discard message and do not increment sequence number
1485                 logger.log(Level.INFO, "DIGEST41:Unmatched MACs");
1486                 return EMPTY_BYTE_ARRAY;
1487             }
1488 
1489             /* Ensure sequence number is correct */
1490             if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
1491                 throw new SaslException("DIGEST-MD5: Out of order " +
1492                     "sequencing of messages from server. Got: " +
1493                     networkByteOrderToInt(seqNum, 0, 4) + " Expected: " +
1494                     peerSeqNum);
1495             }
1496 
1497             /* Check message type */
1498             if (!Arrays.equals(messageType, msgType)) {
1499                 throw new SaslException("DIGEST-MD5: invalid message type: " +
1500                     networkByteOrderToInt(msgType, 0, 2));
1501             }
1502 
1503             // Increment sequence number and return message
1504             peerSeqNum++;
1505 
1506             if (msgLength == msgWithPadding.length) {
1507                 return msgWithPadding; // no padding
1508             } else {
1509                 // Get a copy of the message without padding
1510                 byte[] clearMsg = new byte[msgLength];
1511                 System.arraycopy(msgWithPadding, 0, clearMsg, 0, msgLength);
1512                 return clearMsg;
1513             }
1514         }
1515     }
1516 
1517     // ---------------- DES and 3 DES key manipulation routines
1518 
1519     private static final BigInteger MASK = new BigInteger("7f", 16);
1520 
1521     /**
1522      * Sets the parity bit (0th bit) in each byte so that each byte
1523      * contains an odd number of 1's.
1524      */
1525     private static void setParityBit(byte[] key) {
1526         for (int i = 0; i < key.length; i++) {
1527             int b = key[i] & 0xfe;
1528             b |= (Integer.bitCount(b) & 1) ^ 1;
1529             key[i] = (byte) b;
1530         }
1531     }
1532 
1533     /**
1534      * Expands a 7-byte array into an 8-byte array that contains parity bits
1535      * The binary format of a cryptographic key is:
1536      *     (B1,B2,...,B7,P1,B8,...B14,P2,B15,...,B49,P7,B50,...,B56,P8)
1537      * where (B1,B2,...,B56) are the independent bits of a DES key and
1538      * (PI,P2,...,P8) are reserved for parity bits computed on the preceding
1539      * seven independent bits and set so that the parity of the octet is odd,
1540      * i.e., there is an odd number of "1" bits in the octet.
1541      */
1542     private static byte[] addDesParity(byte[] input, int offset, int len) {
1543         if (len != 7)
1544             throw new IllegalArgumentException(
1545                 "Invalid length of DES Key Value:" + len);
1546 
1547         byte[] raw = new byte[7];
1548         System.arraycopy(input, offset, raw, 0, len);
1549 
1550         byte[] result = new byte[8];
1551         BigInteger in = new BigInteger(raw);
1552 
1553         // Shift 7 bits each time into a byte
1554         for (int i=result.length-1; i>=0; i--) {
1555             result[i] = in.and(MASK).toByteArray()[0];
1556             result[i] <<= 1;         // make room for parity bit
1557             in = in.shiftRight(7);
1558         }
1559         setParityBit(result);
1560         return result;
1561     }
1562 
1563     /**
1564      * Create parity-adjusted keys suitable for DES / DESede encryption.
1565      *
1566      * @param input A non-null byte array containing key material for
1567      * DES / DESede.
1568      * @param desStrength A string specifying eithe a DES or a DESede key.
1569      * @return SecretKey An instance of either DESKeySpec or DESedeKeySpec.
1570      *
1571      * @throws NoSuchAlgorithmException if the either the DES or DESede
1572      * algorithms cannote be lodaed by JCE.
1573      * @throws InvalidKeyException if an invalid array of bytes is used
1574      * as a key for DES or DESede.
1575      * @throws InvalidKeySpecException in an invalid parameter is passed
1576      * to either te DESKeySpec of the DESedeKeySpec constructors.
1577      */
1578     private static SecretKey makeDesKeys(byte[] input, String desStrength)
1579         throws NoSuchAlgorithmException, InvalidKeyException,
1580             InvalidKeySpecException {
1581 
1582         // Generate first subkey using first 7 bytes
1583         byte[] subkey1 = addDesParity(input, 0, 7);
1584 
1585         KeySpec spec = null;
1586         SecretKeyFactory desFactory =
1587             SecretKeyFactory.getInstance(desStrength);
1588 
1589         if (desStrength.equals("des")) {
1590             spec = new DESKeySpec(subkey1, 0);
1591             if (logger.isLoggable(Level.FINEST)) {
1592                 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1593                     "DIGEST42:DES key input: ", input);
1594                 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1595                     "DIGEST43:DES key parity-adjusted: ", subkey1);
1596                 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1597                     "DIGEST44:DES key material: ", ((DESKeySpec)spec).getKey());
1598                 logger.log(Level.FINEST, "DIGEST45: is parity-adjusted? {0}",
1599                     Boolean.valueOf(DESKeySpec.isParityAdjusted(subkey1, 0)));
1600             }
1601 
1602         } else if (desStrength.equals("desede")) {
1603 
1604             // Generate second subkey using second 7 bytes
1605             byte[] subkey2 = addDesParity(input, 7, 7);
1606 
1607             // Construct 24-byte encryption-decryption-encryption sequence
1608             byte[] ede = new byte[subkey1.length*2+subkey2.length];
1609             System.arraycopy(subkey1, 0, ede, 0, subkey1.length);
1610             System.arraycopy(subkey2, 0, ede, subkey1.length, subkey2.length);
1611             System.arraycopy(subkey1, 0, ede, subkey1.length+subkey2.length,
1612                 subkey1.length);
1613 
1614             spec = new DESedeKeySpec(ede, 0);
1615             if (logger.isLoggable(Level.FINEST)) {
1616                 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1617                     "DIGEST46:3DES key input: ", input);
1618                 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1619                     "DIGEST47:3DES key ede: ", ede);
1620                 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1621                     "DIGEST48:3DES key material: ",
1622                     ((DESedeKeySpec)spec).getKey());
1623                 logger.log(Level.FINEST, "DIGEST49: is parity-adjusted? ",
1624                     Boolean.valueOf(DESedeKeySpec.isParityAdjusted(ede, 0)));
1625             }
1626         } else {
1627             throw new IllegalArgumentException("Invalid DES strength:" +
1628                 desStrength);
1629         }
1630         return desFactory.generateSecret(spec);
1631     }
1632 }